1 module hip.data.json; 2 3 4 JSONValue parseJSON(string jsonData) 5 { 6 return JSONValue.parse(jsonData); 7 } 8 struct JSONArray 9 { 10 JSONValue[] value; 11 } 12 struct JSONObject 13 { 14 JSONValue[string] value; 15 16 alias value this; 17 } 18 private enum JSONState 19 { 20 key, 21 lookingAssignment, 22 lookingForNext, 23 value 24 } 25 26 enum JSONType 27 { 28 bool_, 29 float_, 30 int_, 31 string_, 32 array, 33 object 34 } 35 36 bool isWhitespace(char ch){return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';} 37 bool isNumber(char ch){return ch >= '0' && ch <= '9';} 38 bool isNumeric(char ch){return (ch >= '0' && ch <= '9') || ch == '-' || ch == '.';} 39 40 struct JSONValue 41 { 42 union JSONData{ 43 44 float _float; 45 int _int; 46 bool _bool; 47 string _string; 48 JSONObject* object; 49 JSONArray* array; 50 } 51 JSONData data; 52 string key; 53 string error; 54 JSONType type = JSONType.object; 55 56 int integer() const {return get!int;} 57 bool boolean() const {return get!bool;} 58 string str() const {return get!string;} 59 ///Returns an array range. 60 auto array() const 61 { 62 assert(type == JSONType.array, "Tried to iterate a non array object of type "~getTypeName); 63 struct JSONValueArrayIterator 64 { 65 private const(JSONArray*) arr; 66 private size_t idx = 0; 67 size_t length(){return arr.value.length;} 68 bool empty(){return idx == arr.value.length;} 69 void popFront(){idx++;} 70 JSONValue front(){return arr.value[idx];} 71 } 72 return JSONValueArrayIterator(data.array); 73 } 74 75 JSONValue object() 76 { 77 assert(type == JSONType.object, "Tried to get type object but value is of type "~getTypeName); 78 return JSONValue(data, key, error, JSONType.object); 79 } 80 81 string getTypeName() const 82 { 83 final switch(type) with(JSONType) 84 { 85 case int_: return "int"; 86 case bool_: return "bool"; 87 case float_: return "float"; 88 case string_: return "string"; 89 case object: return "object"; 90 case array: return "array"; 91 } 92 } 93 94 T get(T)() const 95 { 96 static if(is(T == int)) 97 { 98 assert(type == JSONType.int_, "Tried to get type "~T.stringof~" but value is of type "~getTypeName); 99 return data._int; 100 } 101 else static if(is(T == float)) 102 { 103 assert(type == JSONType.float_, "Tried to get type "~T.stringof~" but value is of type "~getTypeName); 104 return data._float; 105 } 106 else static if(is(T == bool)) 107 { 108 assert(type == JSONType.bool_, "Tried to get type "~T.stringof~" but value is of type "~getTypeName); 109 return data._bool; 110 } 111 else static if(is(T == string)) 112 { 113 assert(type == JSONType.string_, "Tried to get type "~T.stringof~" but value is of type "~getTypeName); 114 return data._string; 115 } 116 else static if(is(T == JSONObject)) 117 { 118 assert(type == JSONType.object, "Tried to get type "~T.stringof~" but value is of type "~getTypeName); 119 return *data.object; 120 } 121 else static if(is(T == JSONArray)) 122 { 123 assert(type == JSONType.array, "Tried to get type "~T.stringof~" but value is of type "~getTypeName); 124 return *data.array; 125 } 126 } 127 private static JSONValue create(T)(T data, string key) 128 { 129 JSONValue ret; 130 ret.key = key; 131 static if(is(T == int)) 132 { 133 ret.type = JSONType.int_; 134 ret.data._int = data; 135 } 136 else static if(is(T == float)) 137 { 138 ret.type = JSONType.float_; 139 ret.data._float = data; 140 } 141 else static if(is(T == bool)) 142 { 143 ret.type = JSONType.bool_; 144 ret.data._bool = data; 145 } 146 else static if(is(T == string)) 147 { 148 ret.type = JSONType.string_; 149 ret.data._string = data; 150 } 151 else static if(is(T == JSONObject*)) 152 { 153 ret.type = JSONType.object; 154 ret.data.object = data; 155 } 156 else static if(is(T == JSONArray*)) 157 { 158 ret.type = JSONType.array; 159 ret.data.array = data; 160 } 161 else static assert(false, "Unsupported type "~T.stringof); 162 return ret; 163 } 164 165 166 private static JSONValue parse(string data) 167 { 168 import hip.util.conv:to; 169 if(!data.length) 170 { 171 return JSONValue(JSONData.init, "", "No data provided"); 172 } 173 ptrdiff_t index = 0; 174 while(index < data.length && data[index++] != '{'){} 175 if(index == data.length) 176 { 177 return JSONValue(JSONData.init, "", "Valid JSON starts with a '{'."); 178 } 179 180 bool getNextString(string data, ptrdiff_t currentIndex, out ptrdiff_t newIndex, out string theString) 181 { 182 assert(data[currentIndex] == '"'); 183 newIndex = currentIndex+1; 184 ptrdiff_t left = newIndex; 185 while(newIndex < data.length && data[newIndex] != '"') 186 { 187 if(data[newIndex] == '\\') 188 { 189 theString~= data[left..newIndex]; 190 newIndex++; 191 left = newIndex; 192 } 193 newIndex++; 194 } 195 if(newIndex == data.length) //Not found 196 return false; 197 theString~= data[left..newIndex]; //Assign output 198 return true; 199 } 200 201 bool getNextNumber(string data, ptrdiff_t currentIndex, out ptrdiff_t newIndex, out JSONData theData, out JSONType type) 202 { 203 assert(data[currentIndex].isNumeric); 204 bool hasDecimal = false; 205 newIndex = currentIndex; 206 if(data[currentIndex] == '-') 207 newIndex++; 208 if(data[newIndex] == '.') 209 { 210 hasDecimal = true; 211 newIndex++; 212 } 213 214 while(newIndex < data.length) 215 { 216 if(!hasDecimal && data[newIndex] == '.') 217 { 218 if(!hasDecimal) hasDecimal = true; 219 if(newIndex+1 < data.length) newIndex++; 220 } 221 if(isNumber(data[newIndex])) 222 newIndex++; 223 else 224 break; 225 } 226 if(hasDecimal) 227 { 228 theData._float = to!float(data[currentIndex..newIndex]); 229 type = JSONType.float_; 230 } 231 else 232 { 233 theData._int = to!int(data[currentIndex..newIndex]); 234 type = JSONType.int_; 235 } 236 //Stopped on a non number. Revert 1 step. 237 newIndex--; 238 return newIndex < data.length; 239 } 240 JSONValue ret; 241 ret.data.object = new JSONObject(); 242 JSONValue* current = &ret; 243 JSONState state = JSONState.lookingForNext; 244 JSONValue lastValue = ret; 245 246 scope JSONValue[] stack = [ret]; 247 void pushNewScope(JSONValue val) 248 { 249 assert(val.type == JSONType.object || val.type == JSONType.array, "Unexpected push."); 250 JSONValue* currTemp = current; 251 stack~= val; 252 current = &stack[$-1]; 253 if(currTemp.type == JSONType.object) 254 currTemp.data.object.value[val.key] = *current; 255 else 256 currTemp.data.array.value~= *current; 257 } 258 void popScope() 259 { 260 assert(stack.length > 0, "Unexpected pop."); 261 stack = stack[0..$-1]; 262 if(stack.length > 0) 263 { 264 current = &stack[$-1]; 265 assert(current.type == JSONType.object || current.type == JSONType.array, "Unexpected value in stack. (Typed "~(cast(size_t)(current.type)).to!string); 266 } 267 } 268 269 void pushToStack(JSONValue val) 270 { 271 switch(current.type) with(JSONType) 272 { 273 case object: 274 current.data.object.value[val.key] = val; 275 break; 276 case array: 277 current.data.array.value~= val; 278 break; 279 default: assert(false, "Unexpected stack type: "~current.getTypeName); 280 } 281 lastValue = val; 282 } 283 284 size_t line = 0; 285 string getErr(string err="") 286 { 287 return "Error at line "~line.to!string~" "~err~" on index '"~index.to!string~"' last parsed: "~lastValue.toString; 288 } 289 290 string lastKey; 291 do 292 { 293 char ch = data[index]; 294 switch(ch) 295 { 296 case '\n': 297 line++; 298 break; 299 case '{': 300 { 301 if(state != JSONState.value) 302 return JSONValue(JSONData.init, "", getErr()); 303 JSONValue obj = JSONValue.create(new JSONObject(), lastKey); 304 pushNewScope(obj); 305 306 state = JSONState.key; 307 break; 308 } 309 case '}': 310 popScope(); 311 state = JSONState.lookingForNext; 312 break; 313 case ':': 314 if(state != JSONState.lookingAssignment) 315 return JSONValue(JSONData.init, "", getErr("expected key before ':'")); 316 state = JSONState.value; 317 break; 318 case '"': 319 { 320 321 switch(state) 322 { 323 case JSONState.lookingForNext: 324 if(current.type == JSONType.object) 325 goto case JSONState.key; 326 else if(current.type == JSONType.array) 327 goto case JSONState.value; 328 goto default; 329 case JSONState.key: 330 { 331 assert(current.type == JSONType.object, getErr("only object can receive a key.")); 332 if(!getNextString(data, index, index, lastKey)) 333 return JSONValue(JSONData.init, "", getErr("unclosed quotes.")); 334 pushToStack(JSONValue(JSONData.init, lastKey)); 335 state = JSONState.lookingAssignment; 336 break; 337 } 338 case JSONState.value: 339 { 340 string val; 341 if(!getNextString(data, index, index, val)) 342 return JSONValue(JSONData.init, "", getErr("unclosed quotes.")); 343 pushToStack(JSONValue.create!string(val, lastKey)); 344 state = JSONState.lookingForNext; 345 break; 346 } 347 default: 348 return JSONValue(JSONData.init, "", getErr("comma expected before key "~lastValue.key)); 349 } 350 break; 351 } 352 case '[': 353 { 354 if(state != JSONState.lookingForNext && state != JSONState.value) 355 return JSONValue(JSONData.init, "", getErr(" expected to be a value. ")); 356 pushNewScope(JSONValue.create(new JSONArray(), lastKey)); 357 state = JSONState.value; 358 break; 359 } 360 case ']': 361 if(state != JSONState.lookingForNext && state != JSONState.value) 362 return JSONValue(JSONData.init, "", getErr("expected to be a value. ")); 363 popScope(); 364 state = JSONState.lookingForNext; 365 break; 366 case ',': 367 if(state != JSONState.lookingForNext) 368 return JSONValue(JSONData.init, "", getErr("unexpected comma. ")); 369 if(current.type != JSONType.object && current.type != JSONType.array) 370 return JSONValue(JSONData.init, "", getErr("unexpected comma. ")); 371 372 switch(current.type) with(JSONType) 373 { 374 case object: state = JSONState.key; break; 375 case array: state = JSONState.lookingForNext; break; 376 default: assert(false, "Error?"); 377 } 378 break; 379 default: 380 switch(state) 381 { 382 case JSONState.value: //Any value 383 if(ch.isNumeric) 384 { 385 if(!getNextNumber(data, index, index, lastValue.data, lastValue.type)) 386 return JSONValue(JSONData.init, "", getErr("unexpected end of file.")); 387 pushToStack(lastValue); 388 state = JSONState.lookingForNext; 389 } 390 else if(index + "true".length < data.length && data[index.."true".length + index] == "true") 391 { 392 pushToStack(JSONValue.create!bool(true, lastKey)); 393 state = JSONState.lookingForNext; 394 } 395 else if(index + "false".length < data.length && data[index.."false".length + index] == "false") 396 { 397 pushToStack(JSONValue.create!bool(false, lastKey)); 398 state = JSONState.lookingForNext; 399 } 400 401 break; 402 case JSONState.lookingForNext: //Array 403 if(ch.isNumeric) 404 { 405 if(current.type != JSONType.array) 406 return JSONValue(JSONData.init, "", getErr("unexpected number.")); 407 if(!getNextNumber(data, index, index, lastValue.data, lastValue.type)) 408 return JSONValue(JSONData.init, "", getErr("unexpected end of file.")); 409 pushToStack(lastValue); 410 state = JSONState.lookingForNext; 411 } 412 else if(index + "true".length < data.length && data[index.."true".length + index] == "true") 413 { 414 if(current.type != JSONType.array) 415 return JSONValue(JSONData.init, "", getErr("unexpected number.")); 416 pushToStack(JSONValue.create!bool(true, lastKey)); 417 state = JSONState.lookingForNext; 418 } 419 else if(index + "false".length < data.length && data[index.."false".length + index] == "false") 420 { 421 if(current.type != JSONType.array) 422 return JSONValue(JSONData.init, "", getErr("unexpected number.")); 423 pushToStack(JSONValue.create!bool(false, lastKey)); 424 state = JSONState.lookingForNext; 425 } 426 break; 427 default:break; 428 } 429 break; 430 } 431 index++; 432 } 433 while(index < data.length && stack.length > 0); 434 return ret; 435 } 436 437 JSONValue opIndex(string key) const 438 { 439 assert(type == JSONType.object, "Can't get a member from a non object."); 440 return (*data.object)[key]; 441 } 442 const(JSONValue)* opBinaryRight(string op)(string key) const 443 if(op == "in") 444 { 445 if(type != JSONType.object) return null; 446 return key in *(data.object).value; 447 } 448 JSONValue* opBinaryRight(string op)(string key) 449 if(op == "in") 450 { 451 if(type != JSONType.object) return null; 452 return key in (*data.object).value; 453 } 454 455 int opApply(scope int delegate(string key, JSONValue v) dg) 456 { 457 if(type != JSONType.object) 458 { 459 assert(false, "Can't iterate with key[string] and value[JSONValue] an object of type "~getTypeName); 460 } 461 int result = 0; 462 foreach (k, v ; data.object.value) 463 { 464 result = dg(k, v); 465 if (result) 466 break; 467 } 468 469 return result; 470 } 471 bool hasErrorOccurred(){ return error.length != 0; } 472 473 //selfPrintKey is only used for object. 474 string toString(bool selfPrintkey = true) 475 { 476 if(hasErrorOccurred) 477 return error; 478 import hip.util.conv:to; 479 string ret; 480 final switch ( type ) with(JSONType) 481 { 482 case int_: 483 ret = data._int.to!string; 484 break; 485 case float_: 486 ret = data._float.to!string; 487 break; 488 case bool_: 489 ret = data._bool ? "true" : "false"; 490 break; 491 case string_: 492 ret = '"'~data._string~'"'; 493 break; 494 case array: 495 { 496 ret = "["; 497 bool isFirst = true; 498 foreach(v; data.array.value) 499 { 500 if(!isFirst) 501 ret~=", "; 502 isFirst = false; 503 ret~= v.toString; 504 } 505 ret~= "]"; 506 break; 507 } 508 case object: 509 { 510 if(selfPrintkey) 511 { 512 ret = '"'~key~"\": "; 513 } 514 ret~= '{'; 515 bool isFirst = true; 516 foreach(k, v; data.object.value) 517 { 518 if(!isFirst) 519 ret~= ", "; 520 isFirst = false; 521 ret~= '"'~k~"\" : "~v.toString(false); 522 } 523 ret~= '}'; 524 break; 525 526 } 527 } 528 return ret; 529 } 530 531 void dispose() 532 { 533 if(type == JSONType.object) 534 { 535 foreach(v; data.object.value) 536 v.dispose(); 537 } 538 else if(type == JSONType.array) 539 { 540 foreach(v; data.array.value) 541 v.dispose(); 542 } 543 544 } 545 }